/*
 * Copyright 2007 - The JDPF Project Team (http://www.jdpf.org) 
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); 
 * you may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at 
 * 
 * http://www.apache.org/licenses/LICENSE-2.0 
 * 
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS, 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and 
 * limitations under the License. 
 */
package org.jdpf.plugins.measureseries.filter.measureseries.quantityrangefilter;

import java.util.List;

import javax.measure.Measure;
import javax.measure.unit.Unit;

import org.jdpf.core.kernel.elements.Token;
import org.jdpf.core.plugins.blocks.AbstractFilterPlugin;
import org.jdpf.core.plugins.exceptions.PluginException;
import org.jdpf.core.plugins.exceptions.PluginExceptionsEnum;
import org.jdpf.plugins.datatypes.basic.MeasureAdapter;
import org.jdpf.plugins.datatypes.basic.MeasureSeries;
import org.jscience.physics.amount.Amount;

/**
 * The Range Filter filters all the values out of a range (defined through 
 * minimum and/or maximum value allowed). The values that are out of range 
 * can be rejected or clipped (changed to the closest allowed value).
 * 
 * @author Cristiana Larizza
 * @author Paolo Ciccarese
 * @version 1.0
 */

public class QuantityRangeFilter extends AbstractFilterPlugin {
	
	/*
	 * Parameters list. We prefer to define the plugin parameters with an 
	 * underscore. For every plugin parameter it is necessary to implement 
	 * a 'set' method that is accepting a String variable. The framework,
	 * through reflection, will set the parameters at runtime. 
	 */
	private Boolean _enableClipping = true;	// Optional
	
	private MeasureAdapter _lowerBound;		// Optional
	private MeasureAdapter _upperBound;		// Optional
	
	private Unit _unitOfMeasure = Unit.ONE;	// Optional

	/**
	 * The clipping parameter establish if the out of range value has to 
	 * be rejected or clipped to the closest bound.
	 * @param enableClipping
	 */
	public void setEnableClipping(Boolean enableClipping) {
		this._enableClipping = enableClipping;
	}

	/**
	 * @param lowerBound	The lower bound in double precision.
	 */
	public void setLowerBound(Double lowerBound) {
		this._lowerBound = new MeasureAdapter(Measure.valueOf(lowerBound, Unit.ONE));
	}

	/**
	 * @param upperBound	The upper bound in double precision.
	 */
	public void setUpperBound(Double upperBound) {
		this._upperBound = new MeasureAdapter(Measure.valueOf(upperBound, Unit.ONE));
	}
	
	public void setUnitOfMeasure(String unitOfMeasure) {
		_unitOfMeasure = Unit.valueOf(unitOfMeasure.subSequence(0, unitOfMeasure.length()));
	}
	
	/*
	 * Standard plugin constructor: every plugin has to implement the 
	 * constructor in this very same format.
	 */
	public QuantityRangeFilter(String transitionId, String transitionName){
		super(transitionId, transitionName);
	}
	
	/*
	 * This is the actual algorithm implementation. All the exceptions 
	 * generated by this code are catched in the upper level. This 
	 * method can access the cvariable 'percentage' that is used by the
	 * monitors to show the elaboration progress. 
	 */
	public Token process(List<Token> list) throws PluginException {
		
		_upperBound = new MeasureAdapter(Measure.valueOf(_upperBound.getMeasure().doubleValue(Unit.ONE), _unitOfMeasure));
		_lowerBound = new MeasureAdapter(Measure.valueOf(_lowerBound.getMeasure().doubleValue(Unit.ONE), _unitOfMeasure));
		
		if(_upperBound.getMeasure().doubleValue(_unitOfMeasure) < _lowerBound.getMeasure().doubleValue(_unitOfMeasure))
			throw new PluginException (PluginExceptionsEnum.IllegalParameter, this.getPluginId(),
					"The parameters '_lowerBound' and '_upperBound' values are not valid. _lowerBound must be < _upperBound.");
		// It reads only the first list in the token.
		List inList = list.get(0).getValueList();

		// It reads only the first list in the token.
		MeasureSeries ms = (MeasureSeries) inList.get(0);

		MeasureAdapter inputValue;
		MeasureSeries msout = new MeasureSeries();
		
		for (int i=0; i<ms.getSize(); i++){
			if(!start) break; 									// Allows the task to block the elaboration
			percentage = ((double)(i+1) / ms.getSize()) * 100; 	// Elaboration progress
			inputValue = ((MeasureAdapter) ms.getElement(i));	
			this.log(this, "reading " + inputValue.getMeasure().doubleValue(inputValue.getMeasure().getUnit()) + " unit: " + inputValue.getMeasure().getUnit().toString());
			
			if (getAmount(inputValue).isLargerThan(getAmount(_lowerBound)) &&
					getAmount(inputValue).isLessThan(getAmount(_upperBound))) {
				this.log(this, "writing " + inputValue.getMeasure().doubleValue(inputValue.getMeasure().getUnit()));
				msout.addElement(inputValue);
			} else if (_enableClipping) {
				if(getAmount(inputValue).isLessThan(getAmount(_lowerBound))) {
					this.log(this, "writing lower bound " + _lowerBound.getMeasure().doubleValue(_unitOfMeasure));
					inputValue.setMeasure(_lowerBound.getMeasure());
					msout.addElement(inputValue);
				} else {
					this.log(this, "writing upper bound " + _upperBound.getMeasure().doubleValue(_unitOfMeasure));
					inputValue.setMeasure(_upperBound.getMeasure());
					msout.addElement(inputValue);
				}
			}
		}
		
   	return setOutputToken(msout); // Defines and returns the output token
	}
	
	private Amount getAmount(MeasureAdapter ma) {
		return Amount.valueOf(ma.getMeasure().doubleValue(_unitOfMeasure), _unitOfMeasure);
	}
}
